package mindwave.theoracle;


// MindReader.java
// Andrew Davison, October 2012, ad@fivedots.psu.ac.th

/* Connect to the MindWave and read its Attention, Meditation,
   and BlinkStrength values roughly every second. These values
   are added to the end of the relevant JFreeChart data sets, which
   will cause the JfreeChart chart to update.

   The Attention and Meditation values are displayed as two line charts,
   while the Blink Strength is shown as bars. All the values can range
   between 0-100, which means that the blink strength is scaled from the
   range 0-255.

   This MindReader class is based on the ReadMindWave example. 
   However, instead of running for a fixed amount of time, it
   executes in a loop inside a thread until instructed to stop by 
   setting a isRunning boolean to false. This is done by MindWaver
   when its close box is clicked.

*/

import java.awt.*;
import javax.swing.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;

import org.jfree.data.xy.*;



public class MindReader implements Runnable
{

  private static final int COM_PORT = 6;
  private static final int POLL_INTERVAL = 50;   // ms

          // how frequently to read the MindWave data

  private static final long REPORT_INTERVAL = 2;   // ms
          // how frequently to report the MindWave data to the user


  private static final int STRONG_SIGNAL = 50; 
           // smaller is stronger, with 0 the most powerful


  private ThinkGear tg = ThinkGear.INSTANCE;
  private int connID;
  private boolean hasBlinked = false;
       /* this flag is used to remember a blink strength update
          until the value is printed out */

  private XYSeries attnSeries, mediSeries, blickSeries, rawSeries;
       // the JFreeChart data sets for attention, meditation, blink strength and raw signal

  private volatile boolean isRunning = false;



  public MindReader(XYSeries aSeries, XYSeries mSeries, XYSeries bSeries, XYSeries rSeries)
  {
    attnSeries = aSeries;
    mediSeries = mSeries;
    blickSeries = bSeries;
    rawSeries = rSeries;

    connID = connect(COM_PORT);
    tg.TG_EnableBlinkDetection(connID, 1);

    new Thread(this).start();   // start polling the MindWave
  } // end of MindReader()



  private int connect(int comPort)
  // connect to the MindWave, returning the connection ID
  {
    int connID = tg.TG_GetNewConnectionId();
    if(connID < 0) {
      System.out.println("Could not get a connection ID");
      System.exit(1);
    }

    int connResult = tg.TG_Connect(connID, "\\\\.\\COM" + comPort, 
                                 ThinkGear.BAUD_115200, ThinkGear.STREAM_PACKETS);
    if(connResult < 0) {
      System.out.println("Could not connect to COM " + comPort);
      System.exit(1);
    }

    return connID;
  }  // end of connect()



  public void run()
  /* poll the MindWave, and report useful info roughl every second
  */
  {
    isRunning = true;
    long reportTime = System.currentTimeMillis();
    int reportCount = 0;

    while (isRunning) {
      readPackets(connID);
      if ((System.currentTimeMillis() - reportTime) > REPORT_INTERVAL) {
        if (!hasRaw(connID))
          System.out.println("no data");
        else if (!hasSignal(connID))
          System.out.println("low signal");
        else {
          reportWaves(connID, reportCount);
          reportCount++;
        }
        reportTime = System.currentTimeMillis();
      }
      try {
        Thread.sleep(POLL_INTERVAL);
      }
      catch (InterruptedException e) {}
    }

    // close down
    tg.TG_FreeConnection(connID);
    System.exit(1);
  }  // end of run()


  public void closeDown()
  {  isRunning = false;  } 



  private int readPackets(int connID)
  /* read as many packets are currently available. 
     TGCD will use them to update the currently values
     for the different Wave types and set their status to 'true'
     (i.e. updated). 
  */
  {
    int numPackets = tg.TG_ReadPackets(connID, -1);    // read all available packets

    if (numPackets == -1) {
      System.out.println("Invalid connection ID");
      System.exit(1);
    }
    else if (numPackets == -2) {
    //  System.out.println("No packets available");
      return 0;
    }
    else if (numPackets == -3) {
      System.out.println("Serial stream read error");
      System.exit(1);
    }
    // else 
    //   System.out.println("No. packets: " + numPackets);

    if (isUpdated(connID, Wave.BlinkStrength))
      hasBlinked = true;  
      /* used to remember that there's been a blink strength update 
         until after the value has been printed */

    return numPackets;
  }  // end of readPackets()



  private void reportWaves(int connID, int reportCount)
  /* report information about the attention, meditation 
     and blink strength waves, both to stdout and onto the chart
  */
  {
    System.out.println("---- Report (" + reportCount + ")----");
    
    double raw = get(connID, Wave.Raw);
    System.out.println("  Raw: " + raw);
    rawSeries.add(reportCount, raw); 
    
    double attention = get(connID, Wave.Attention);
    System.out.println("  Attention: " + attention);
    attnSeries.add(reportCount, attention); 

    double meditation = get(connID, Wave.Meditation);
    System.out.println("  Meditation: " + meditation);
    mediSeries.add(reportCount, meditation); 

    
    if (hasBlinked) {
      double bs = get(connID, Wave.BlinkStrength);
      System.out.println("  Blink Strength: " +  bs);
      blickSeries.add(reportCount, bs/2.55);     // scale 0-255 ==> 0-100
      hasBlinked = false;     // reset flag after the value is printed
    }
    else     // don't keep showing the old value
      blickSeries.add(reportCount, 0); 
  }  // end of reportWaves()



  private double get(int connID, Wave wave)
  {  return tg.TG_GetValue(connID, wave.getCode());  }


  private boolean isUpdated(int connID, Wave wave)
  {  return tg.TG_GetValueStatus(connID, wave.getCode()) != 0;  }


  private boolean hasSignal(int connID)
  // is the signal strong enough? 
  { 
    double signal = get(connID, Wave.PoorSignal);
    if (signal == 200) {   // special signal value
      System.out.println("ThinkGear contacts not touching the skin");
      return false;
    }
    return (signal < STRONG_SIGNAL);
  }  // end of hasSignal()


  private boolean hasRaw(int connID)
  /* if there's no raw data then there's a problem with the 
     the connection to the MindWave */
  {  return (get(connID, Wave.Raw) != 0);  }


} // end of MindReader class

